package com.thomas.framework.utils.mybatis;

import com.thomas.framework.utils.PageResult;
import com.thomas.framework.utils.ReflectUtil;
import org.apache.ibatis.executor.ExecutorException;
import org.apache.ibatis.executor.statement.PreparedStatementHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
import org.apache.log4j.Logger;
import org.springframework.util.StringUtils;

import java.sql.*;
import java.util.List;
import java.util.Properties;

/**
 * Created by taohj on 2014/4/17.
 * 不在拦截器中计算总数，影响效率
 *
 *  分页拦截器，用于拦截需要进行分页查询的操作，然后对其进行分页处理。 利用拦截器实现Mybatis分页的原理：
 *  要利用JDBC对数据库进行操作就必须要有一个对应的Statement对象,Mybatis在执行Sql语句前就会产生一个包含Sql语句的Statement对象，而且对应的Sql语句
 *  是在Statement之前产生的，所以我们就可以在它生成Statement之前对用来生成Statement的Sql语句下手。
 *
 *  在Mybatis中Statement语句是通过RoutingStatementHandler对象的prepare方法生成的。
 *  所以利用拦截器实现Mybatis分页的一个思路就是拦截StatementHandler接口的prepare方法 ，然后在拦截器方法中把Sql语句改成对应的分页查询Sql语句，
 *  之后再调用StatementHandler对象的prepare方法，即调用invocation.proceed()。
 *  对于分页而言，在拦截器里面我们还需要做的一个操作就是统计满足当前条件的记录一共有多少，这是通过获取到了原始的Sql语句后，把它改为对应的统计语句再利用Mybatis封装好的参数和设
 *  置参数的功能把Sql语句中的参数进行替换，之后再执行查询记录数的Sql语句进行总记录数的统计。
 */
@Intercepts({ @Signature(method = "prepare", type = StatementHandler.class, args = { Connection.class }) })
public class MybatisPageInterceptor  implements Interceptor{

    Logger logger=Logger.getLogger(getClass());





    public PreparedStatementHandler  delegate;



    public     MappedStatement mappedStatement;




    // 分页帮助类，不同的数据库有不同的帮助类
    public   MybatisPageHelpper pageHelpper;





    public void setPageHelpper(MybatisPageHelpper pageHelpper) {
        this.pageHelpper = pageHelpper;
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        //初始化

        delegate= (PreparedStatementHandler) ReflectUtil.getFieldValue((invocation.getTarget()),"delegate");
        mappedStatement = (MappedStatement) ReflectUtil.getFieldValue(delegate,"mappedStatement");



        //logger.info(mappedStatement.getConfiguration().getDatabaseId());



        // 是通过Interceptor的plugin方法进行包裹的，所以我们这里拦截到的目标对象肯定是RoutingStatementHandler对象。
        final RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget();

        // 通过反射获取到当前RoutingStatementHandler对象的delegate属性，获取到当前StatementHandler的
        final StatementHandler delegate = (StatementHandler) ReflectUtil.getFieldValue(handler, "delegate");

        // boundSql，这里不管是调用handler.getBoundSql()还是直接调用delegate.getBoundSql()结果是一样的，因为之前已经说过了
        // RoutingStatementHandler实现的所有StatementHandler接口方法里面都是调用的delegate对应的方法。
        final BoundSql boundSql = delegate.getBoundSql();


        // 拿到当前绑定Sql的参数对象，就是我们在调用对应的Mapper映射语句时所传入的参数对象
        final Object obj = boundSql.getParameterObject();


        // 这里我们简单的通过传入的是Page对象就认定它是需要进行分页操作的。
        if (obj instanceof PageResult){

            PageResult pageResult= (PageResult) obj;
            pageHelpper.setPageResult(pageResult);

            StringBuffer sql=new StringBuffer(boundSql.getSql());




            //获得当前参数对象的总记录数
            // 拦截到的prepare方法参数是一个Connection对象
           StringBuffer countSql=new StringBuffer(sql);
            Connection connection = (Connection) invocation.getArgs()[0];
            this.setPageResultTotal(pageResult,connection,countSql,boundSql.getParameterMappings());







            //自定义排序
            if (StringUtils.hasLength(pageResult.getSort()) && StringUtils.hasLength(pageResult.getOrder())) {
                sql.append(" order by ").append(pageResult.getSort()).append(" ").append(pageResult.getOrder());
            }


            //获得分页sql语句
            final String pageSql = pageHelpper.getPageSql(sql,mappedStatement.getConfiguration().getDatabaseId());


            // 利用反射设置当前BoundSql对应的sql属性为我们建立好的分页Sql语句
            ReflectUtil.setFieldValue(boundSql, "sql", pageSql);




        }

        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {


    }



    private  void setPageResultTotal(PageResult pageResult,Connection connection,StringBuffer sql,List<ParameterMapping> parameterMappings){

        sql.insert(0, "select count(1) from  ( ").append(" )  cnt ");


        logger.debug("countSql:----------------=>"+sql.toString());


        PreparedStatement preparedStatement=null;
        ResultSet resultSet=null;

        try {
             preparedStatement=connection.prepareStatement(sql.toString());
            setParameters(preparedStatement,parameterMappings,pageResult);

          /*  if (parameterMappings!=null){
                for (int i = 0; i < parameterMappings.size(); i++) {
                    preparedStatement.setStri
                }
            }*/

             resultSet=preparedStatement.executeQuery();
             resultSet.next();
            int total=resultSet.getInt(1);
            pageResult.setTotal(total);
        }catch (SQLException e){
            e.printStackTrace();
        }finally {
            try {
                if (resultSet!=null){resultSet.close();}
                if (preparedStatement!=null){preparedStatement.close();}
              //  if (connection!=null){connection.close();}
            } catch (SQLException e) {
                    e.printStackTrace();
            }
        }

    }
    /**
     *
     *
     * @preparedStatement
     * @parmeterMappings
     * @parameterObject
     * @throws SQLException
     * 对SQL参数(?)设值,参考org.apache.ibatis.executor.parameter.DefaultParameterHandler
     */


    @SuppressWarnings(value = "unchecked")
    protected void setParameters(PreparedStatement preparedStatement, List<ParameterMapping> parmeterMappings, Object parameterObject) throws SQLException {


        for (int i = 0; i<parmeterMappings.size() ; i++) {
            ParameterMapping parmentMapping=parmeterMappings.get(i);
            String propertyName=parmentMapping.getProperty();

            Object value=getParmeterMappingValue(parameterObject,propertyName);

            TypeHandler typeHandler = parmentMapping.getTypeHandler();
            if (typeHandler == null) {
                throw new ExecutorException("There was no TypeHandler found for parameter "+ propertyName + " of statement "+ mappedStatement.getId());
            }

            JdbcType jdbctype=parmentMapping.getJdbcType();
            if (jdbctype==null){
                jdbctype=JdbcType.forCode(Types.VARCHAR);
            }
           typeHandler.setParameter(preparedStatement, i + 1, value, jdbctype);
        }

    }




    private Object getParmeterMappingValue(Object paramterObject,String propertyName){
        String [] sSplitArray=propertyName.split("\\.");
        Object object=paramterObject;
        if ( sSplitArray.length>0){
            for (String s : sSplitArray) {
                object=ReflectUtil.getFieldValue(object,s);
            }
        }
        return  object;
    }


}
